﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BeanboxSoftware.BeanProxy.Recording
{
  /// <summary>
  /// A MethodGroup defines a group of method calls. The first call to MethodConfig.Expect() 
  /// creates a [hidden] MethodGroup where the `Expect' rules are stored. The MethodGroup will 
  /// then monitor the associated proxy calls.
  /// 
  /// If an In.Sequence rule is broken it throws an InvalidOperationException
  /// If a rule was not satisfied, VerifyRecord will also throw an InvalidOperationException
  /// </summary>
  public class MethodGroup
  {
    /// <summary>
    /// If true, a RuleIntercept throws an InvalidOperationException if a method config enters that does
    /// not have an associated rule.
    /// If false, unassociated intercepts are ignored
    /// False is default
    /// </summary>
    public bool Strict { get; set; }

    private List<MethodRule> _rules;

    public MethodGroup()
    {
      _rules = new List<MethodRule>();
    }

    public void AddRule(MethodRule rule)
    {
      _rules.Add(rule);
    }

    public MulticastDelegate RuleIntercept(MethodConfig mc, params object[] input)
    {
      Interceptor interceptors = null;
      interceptors += SequenceIntercept;
      interceptors += AnyOrderIntercept;

      foreach (var interceptor in interceptors.GetInvocationList())
      {
        var handler = ((Interceptor)interceptor)(mc, input);

        if (handler != null)
          return handler;
      }

      if (Strict)
      {
        throw new InvalidOperationException(
          string.Format("Strict rules are enabled, and no rule satisfies {0} with input [{1}]",
          mc.Method.Name, string.Join(", ", input.Select(o => o == null ? "[null]" : o.ToString()).ToArray())
          ));
      }

      return null;
    }

    protected delegate MulticastDelegate Interceptor(MethodConfig mc, object[] input);
    protected MulticastDelegate SequenceIntercept(MethodConfig mc, object[] input)
    {
      //the first In.Sequence, unsatisfied rule that has not yet been satisfied must
      //match mc:input to be a valid In.Sequence intercept

      var seqRules = _rules.FindAll(r => 
        r.ExpectedCall.RecordType == In.Sequence &&
        !r.IsSatisfied);

      if (seqRules.Count == 0)
      {
        return null;
      }

      //now we know we have at least one unsatisfied sequence rule
      var nextRule = seqRules[0];

      if (nextRule.TryToSatisfy(mc, input))
      {
        return nextRule.Callback;
      }

      //if any other sequence rule satisfies this mc:input, the sequence is broken
      for (int i = 1; i < seqRules.Count; i++)
      {
        if (seqRules[i].TryToSatisfy(mc, input))
        {
          throw new InvalidOperationException(
            string.Format("A call to {0}({1}) was made out of sequence",
            mc.Method.Name, seqRules[i].ExpectedCall.ExpectedArgsList
            ));
        }
      }

      return null;
    }
    protected MulticastDelegate AnyOrderIntercept(MethodConfig mc, object[] input)
    {
      foreach (var rule in _rules.Where(r => r.ExpectedCall.RecordType == In.AnyOrder))
      {
        if (rule.TryToSatisfy(mc, input))
        {
          return rule.Callback;
        }
      }

      return null;
    }

    public virtual void Verify()
    {
      string msg = "Verify all rules failed";

      foreach (var rule in _rules)
      {
        rule.AssertSatisfied(msg);
      }
    }
  }
}
